package calendar;

import java.io.IOException;
import java.io.Serializable;
import java.util.Date;
import java.util.ArrayList;
import java.util.Iterator;

/* this class represents a single user's calendar */
public class CalendarObj implements CalendarObjInt, Serializable
{
  private static final long serialVersionUID = 23048923L;

  CalendarMgr manager;
  User user;
  ArrayList events;
  boolean connected;
  static ArrayList members;
  String type;
  boolean replicaConnected;
  boolean replicaChecked;
  transient CallbackInt client;
  transient CalendarObjInt replica;
  transient String replicaHost;
  transient int replicaPort;

  public CalendarObj( User user, CalendarMgr manager, String type ) throws IOException
  {
    super();
    this.manager = manager;
    this.client = null;
    this.user = user;
    events = new ArrayList();
    connected = false;
    members = new ArrayList();
    this.type = type;
    replicaConnected = false;
    replicaChecked = false;
  }

  public void setCallback( CallbackInt stub )
  {
    this.client = stub;
  }

  public boolean connectReplica( String replicaHost, int replicaPort )
  {
    this.replicaHost = replicaHost;
    this.replicaPort = replicaPort;
    if ( !replicaConnected && this.type.equals("Primary") )
    {
      try
      {
        replica = new CalendarStub( this.replicaHost, this.replicaPort, 
          this.replicaPort, "Calendar" + this.user.getId() );
       // System.out.println( "Connecting calendar object with port: " +
       //   this.replicaPort);
        replicaConnected = true;
      }
      catch (Exception e)
      {
      //  if ( this.type.equals( "Primary" ) && !replicaChecked )
      //    System.out.println( "Cannot connect to Backup calendar object." );
        replicaChecked = true;
        return false;
      }
    }
    return true;
  }

  // check for existence of replica manager
//  public boolean pingReplica()
//  {
//    if ( replicaConnected )
//    {
//      try {
//        replica.isAlive();
//      } catch (IOException ex) {
//        if ( type.equals("Backup") ) type = "PrimaryElect";
//        replicaConnected = false;
//        replicaChecked = false;
//        return false;
//      }
//      return true;
//    }
//    else return false;
//  }

  // dummy method for responding to pingReplica
//  public void isAlive() {}

  public void promote()
  {
    this.type = "PrimaryElect";
  }

  /* retrieve a list of events based on user and start/end dates */
  public synchronized ArrayList retrieveEvents( User user, Date start,
    Date end ) throws IOException
  {
    ArrayList eventList = new ArrayList();
    ArrayList newEventList = new ArrayList();

    if ( this.user.getId() == user.getId() )
    {
      eventList = events;
      newEventList.addAll( 0, eventList );
    }
    else
    {
      CalendarObj calendar = manager.getCalendar( (int)user.getId() );
      eventList = calendar.events;
      newEventList.addAll( 0, eventList );
    }

    if ( start == null && end == null ) return eventList;

    Iterator itr = eventList.iterator();
    while ( itr.hasNext() )
    {
      Event e = (Event)itr.next();

      /* only return events within provided range */
      if ( (e.getStart().compareTo(start) > 0 ||
            e.getStart().compareTo(start) == 0) &&
           (e.getEnd().compareTo(end) < 0 ||
            e.getEnd().compareTo(end) == 0) ) {}
      else newEventList.remove( e );
    }

    if ( newEventList.isEmpty() ) return null;
    else return newEventList;
  }

  /* schedule a new event, modify an event, or delete an event */
  public synchronized ArrayList scheduleEvent(ArrayList users, Event newEvent,
    Event oldEvent, boolean addToThis ) throws IOException
  {
    if ( replicaConnected && (this.type.equals("Primary") ||
         this.type.equals("PrimaryElect")) ) {
      replica.scheduleEvent(users, newEvent, oldEvent, addToThis);
    }
    
    if ( addToThis ) /* add event to this calendar */
    {
      if ( oldEvent != null )
      {
        ArrayList eventsCopy = new ArrayList();
        eventsCopy.addAll( 0, events );

        Iterator itr = eventsCopy.iterator();
        while ( itr.hasNext() )
        {
          Event e = (Event)itr.next();
          if ( e.getId() == oldEvent.getId() ) events.remove( e );
        }
      }

      /* check to ensure this calendar can accommodate this event */
      if ( checkSchedule( this.user, newEvent ) )
      {
        events.add( newEvent );
        members.add( this.user );
        newEvent.addUser( this.user );
        if ( client != null && this.type.indexOf( "Primary" ) > -1
             && newEvent.getType() == 3 )
        {
          client.notifyClientEvent( newEvent, false );
        }
      }
    }

    if ( newEvent == null && oldEvent == null )
    {
      System.out.println( "Error: Null passed in as event arguments to " +
        "CalendarObj: scheduleEvent" );
      manager.writeToDisk();
      return null;
    }

    /* attempt to add the event to the other users calendars (recursion) */
    if ( ((newEvent != null && newEvent.getType() == 3) ||
         ( oldEvent != null && oldEvent.getType() == 3)) && !addToThis )
    {
      addToThis = true;
      Iterator itr = users.iterator();
      while ( itr.hasNext() )
      {
        User u = (User)itr.next();
        manager.getCalendar( (int)u.getId() ).scheduleEvent( users,
          newEvent, oldEvent, addToThis );
      }
    }
    manager.writeToDisk();
    return members;
  }

  /* clear the temporary members list shared by all calendars */
  public synchronized void clearMembers() throws IOException
  {
    members = new ArrayList();
    members.clear();
    manager.writeToDisk();
    if ( replicaConnected && (this.type.equals("Primary") ||
         this.type.equals("PrimaryElect")) )
      replica.clearMembers();
  }

  // register a user interface client with this calendar for callbacks
  public synchronized void registerCallback(CallbackInt client) throws IOException
  {
    if ( this.type.equals( "Backup" ) ) return; /* Backup does not callback */
    this.client = client;
    manager.writeToDisk();
  }

  // unregister a user interface client from this calendar
  public synchronized void unregisterCallback() throws IOException
  {
    if ( this.type.equals( "Backup" ) ) return;
    this.client = null;
    manager.writeToDisk();
  }

//  public char getUserId() throws IOException
//  {
//    return this.user.getId();
//  }

  public User getUser() throws IOException
  {
    return this.user;
  }

  public synchronized ArrayList getEvents()
  {
    return this.events;
  }

  public synchronized void setIsConnected ( boolean connected )
  {
    this.connected = connected;
    manager.writeToDisk();
  }

  public synchronized boolean isConnected () throws IOException
  {
    return connected;
  }

  public void setReplicaConnected( boolean replicaConnected )
  {
    this.replicaConnected = replicaConnected;
  }

  public void setReplicaChecked( boolean replicaChecked )
  {
    this.replicaChecked = replicaChecked;
  }

  /* notify a client that an event is about to occur */
  public synchronized void notifyClientEvent( Event event )
  {
    if ( client != null && this.type.indexOf( "Primary" ) > -1 )
    {
      try
      {
        client.notifyClientEvent( event, true );
      }
      catch(IOException ex)
      {
        System.out.println( "CalendarObj: notifyClientEvent -> " + ex );
      }
    }
    event.setIsSent();
    manager.writeToDisk(); /* TODO: replicate this call? */
  }

  /* notify a client that a new calendar has been created */
  public synchronized void notifyClientUser( User user, ArrayList users )
  {
    if ( client != null && this.type.indexOf( "Primary" ) > -1 )
    {
        try
        {
          client.notifyClientUser( user, users );
        }
        catch(IOException ex)
        {
          System.out.println( "CalendarObj: notifyClientUser -> " + ex );
        }
    }
  }

  /* check the current calendar's schedule for availability */
  private synchronized boolean checkSchedule( User user, Event event )
  {
    if ( user == null )
    {
      return false;
    }

    if ( event == null )
    {
      return false;
    }

    ArrayList eventsToCheck = new ArrayList();

    /* if the user to check owns this calendar */
    if ( this.user.getId() == user.getId() )
    {
      eventsToCheck = this.events;
    }
    else
    {
        try
        {
          eventsToCheck = manager.getCalendar(user.getId()).getEvents();
        }
        catch(IOException ex)
        {
          System.out.println( "CalendarObj: checkSchedule -> " + ex );
        }
    }

    if ( eventsToCheck != null )
    {
      Iterator itr = eventsToCheck.iterator();
      while ( itr.hasNext() )
      {
        Event e = (Event)itr.next();

        /* if the new event starts or ends within this event */
        if ( ( (event.getStart().compareTo( e.getStart() ) > 0) &&
             (event.getStart().compareTo( e.getEnd() ) < 0) ) ||
               ( (event.getEnd().compareTo( e.getStart() ) > 0) &&
             (event.getEnd().compareTo( e.getEnd() ) < 0) ) ||
             event.getStart().compareTo( e.getStart() ) == 0 ||
             event.getEnd().compareTo( e.getEnd() ) == 0 )
        {
          return false;
        }
      }
    }
    return true;
  }
}
